<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <canvas id="container" ></canvas>
  <select name="" id="select">
    <option value="S" selected>小</option>  
    <option value="M" >中</option>  
    <option value="L" >大</option>
  </select>
  <button onclick="start()">开始游戏</button>
  <div id="time"></div>
  <script>
    function WeeperInit(type = 'S') {
      this.dom = {
        select: document.querySelector('#select'),
        container: document.querySelector('#container'),
      };
      this.ctx = this.dom.container.getContext('2d');
      this.sizes = 50;
      this.size = {
        S:{x:9,y:9,mine:10},
        M:{x:16,y:16,mine:40},
        L:{x:30,y:16,mine:99},
      };
      this.width = this.sizes * this.size[type].x + this.size[type].x;
      this.height = this.sizes * this.size[type].y + this.size[type].y;;
      this.color = { // 颜色配置
        box: '#66ccff',
        open: '#0f0',
        mine:'#f00',
        select:'#000',
        border:'1px solid #e3e3e3',
        mine: '#ff0000',
      };
      this.itemArr = [];
      this.re = () => {
        this.dom = '';
        this.itemArr = [];
        this.mineArr = [];
      }
      // 分组
      this.groupArr = (arr, n) => {
        let group = [];
        for(let i = 0; i < arr.length; i += n) {
          group.push(arr.slice(i, i + n));
        }
        return group;
      }
      // 初始化
      this.init = () => {
        this.ctx.clearRect(0,0,this.width,this.height);
        const {x,y,mine} = this.size[type];
        this.dom.container.width = this.width;
        this.dom.container.height = this.height;
        this.dom.container.style.border = this.color.border;
        const pointArr = []
        for(let i = 0; i < y; i++){
          for(let j = 0; j < x; j++){
            pointArr.push({x:j*this.sizes,y:i*this.sizes,open:false,isMine:false,mineNum:'',color:this.color.box,isSelect:false});
          }
        }
        this.itemArr = pointArr;
        this.draw();
        this.mineArr = this.generateMine(x,y,mine);
      }
      // 绘制小方块
      this.drawItem = (x, y, color = '#000000',mineNum = '') => {
        this.ctx.beginPath()
        this.ctx.fillStyle = color;
        this.ctx.fillRect(x + x / this.sizes ,y + y / this.sizes , this.sizes, this.sizes);
        this.ctx.font =  '20px 微软雅黑';
        this.ctx.textAlign = 'center';
        this.ctx.textBaseline = 'middle';
        this.ctx.fillStyle = '#000000';
        this.ctx.fillText(mineNum,x+ x / this.sizes + this.sizes / 2  ,y + y / this.sizes + this.sizes / 2 )
        this.ctx.closePath();
      }
      // 生成随机雷
      this.generateMine = (sx,sy,n) => {
        let mineArr = [];
        for(let i = 0; i < n; i++){
          let x = Math.floor(Math.random() * sx);
          let y = Math.floor(Math.random() * sy);
          let item = mineArr.find(item => item.x === x * this.sizes && item.y === y * this.sizes);
          if(item) continue;
          mineArr.push({x:x * this.sizes,y:y * this.sizes});
          this.itemArr.map(items => {
            if(x*this.sizes === items.x && y*this.sizes === items.y){
              items.open = true;
              items.isMine = true;
            }
          })
        }
        return mineArr;
      }

      // 查找点击坐标对应方块
      this.findItem = (x,y) => {
        let item = this.itemArr.find(item => item.x+this.sizes +x / this.sizes  >= x && item.y+this.sizes + y / this.sizes >= y);
        return item;
      }
      // 判断是否点击到雷
      this.isMine = (x,y) => {
        let item = this.mineArr.find(item => item.x === x && item.y === y);
        return item ? true : false;
      }
      // 计算矩阵周围雷的数量并消除数量为0的矩阵
      this.generateNum = (index = {},arr = []) => {
        const {x,y} = index;
        // 方向
        const direction = {
          leftTop:[-1,-1],
          left:[-1,0],
          leftBottom:[-1,1],
          top:[0,-1],
          bottom:[0,1],
          rightTop:[1,-1],
          right:[1,0],
          rightBottom:[1,1]
        }
        const savePoint = []; // 记录扫描过的坐标防止重复Maximum call
        const opens = (obj = {},current = {}) => {
          if(savePoint.find(item => item.x === current.x*this.sizes && item.y === current.y*this.sizes)) return;
          let num = 0;
          let openArr = []; // 需要打开的坐标及显示数字
          savePoint.push({x:current.x * this.sizes,y:current.y * this.sizes});
          
          for(let i in obj){
            const dx = current.x + obj[i][0];
            const dy = current.y + obj[i][1];
            if(dx < 0 || dx >= this.size[type].x || dy < 0 || dy >= this.size[type].y) continue;
            // 判断是否有雷
            const is =  this.isMine(dx * this.sizes,dy * this.sizes);
            if(!is){
              openArr.push({x:dx,y:dy});
            }else{
              num++;
            }
          }
          if(num > 0){
            const item = this.itemArr.find(items => items.x === current.x*this.sizes && items.y === current.y*this.sizes);
            item.open = true;
            item.mineNum = num;
          }else{
            const fItem = this.itemArr.find(item => item.x === x * this.sizes && item.y === y * this.sizes);
            fItem.open = true;
            for(let i = 0; i < openArr.length; i++){
              const item = openArr[i];
              const Iitem = this.itemArr.find(items => items.x === item.x*this.sizes && items.y === item.y*this.sizes);
              Iitem.open = true;
              // 使用递归计算矩阵周围雷数
              opens(direction,{x:item.x,y:item.y});
            }
          }
        }
        opens(direction,index);
        this.draw();
      }
      // 绘制
      this.draw = () => {
        this.ctx.clearRect(0,0,this.width,this.height);
        for(let i = 0; i < this.itemArr.length; i++){
          const item = this.itemArr[i];
          const color = item.isSelect ? this.color.select : this.color.box;
          if(item.open){
            this.drawItem(item.x,item.y,this.color.open,item.mineNum)
          }else{
            this.drawItem(item.x,item.y,color)
          }
          if(item.isMine){
            this.drawItem(item.x,item.y,color)
          }
        }
      }
      // open
      this.open = ({x,y}) => {
        const groupArr = this.groupArr(this.itemArr,this.size[type].x);
        let index = {x:0,y:0}; // 点击坐标
        // 先找到点击的坐标
        for(let k in groupArr){
          const itemK = groupArr[k];
          for(let j in itemK){
            const itemJ = itemK[j];
            if(itemJ.x === x && itemJ.y === y){
              index = {x:parseInt(j),y:parseInt(k)}
            }
          }
        }
        this.generateNum(index,groupArr);
      }
      // 移除事件
      this.removeEvent = () => {
        this.dom.container.removeEventListener('click',this.handleClick);
        this.dom.container.removeEventListener('mousemove',this.handleMove);
      }
      // 处理点击事件
      this.handleClick = (e) => {
        const canvasInfo = this.dom.container.getBoundingClientRect();
        const x = e.clientX - canvasInfo.left;
        const y = e.clientY - canvasInfo.top;
        const item = this.findItem(x,y);
        const isMine = this.isMine(item.x,item.y);
        if(isMine){
          alert('你被炸死了');
          this.removeEvent()
          for(let i = 0; i < this.mineArr.length; i++){
            this.drawItem(this.mineArr[i].x,this.mineArr[i].y,'#f00')
          }
          this.re()
        }else{
          this.open(item);
          let openNum = 0;
          this.itemArr.map(item => {
            if(item.open) ++openNum;
          });
          if(openNum === this.size[type].x * this.size[type].y){
            this.removeEvent()
            const is = window.confirm("你赢了！！！！！是否开启新游戏");
            if(is){
              this.re()
              new WeeperInit(e.target.value);
            }
          }
        }
      }
      this.prevBox = {isSelect:false,x:0,y:0};
      this.handleMove = (e) => {
        const canvasInfo = this.dom.container.getBoundingClientRect();
        const x = e.clientX - canvasInfo.left;
        const y = e.clientY - canvasInfo.top;
        const item = this.findItem(x,y);
        if(item){
          this.prevBox.isSelect = false;
          item.isSelect = true;
          this.prevBox = item;
          this.draw()
        }
      }
      this.dom.container.addEventListener('click', this.handleClick)
      this.dom.container.addEventListener('mousemove',this.handleMove),
      this.dom.select.addEventListener('change', (e) => {
        this.dom.container.removeEventListener('click', this.handleClick);
        new WeeperInit(e.target.value);
      })
      this.init();
    };
    function start() {
      const val = document.querySelector("#select").value;
      new WeeperInit(val);
    }
    window.onload = function () {
      new WeeperInit();
    };
  </script>
</body>
</html>